%% The MIT License
%% 
%% Paralog is licensed under the MIT License.
%% 
%% Copyright (c) 2008 Robert Ahrens <mindangels@yahoo.com>
%% 
%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%% 
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%% 
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% 

%% @author Robert Ahrens <mindangels@yahoo.com>
-module(pl_compiler).

-export([
         kb/1,
         module/2,
         file/2
        ]).


kb(KBToks) when is_list(KBToks) ->
    [comp_rule(KI) || KI <- KBToks].


comp_rule({rule, {atom, _, RuleName}, Head, Body})
  when is_list(Head) andalso is_list(Body) ->
    pls:rule(RuleName, comp_expr_list(Head), comp_expr_list(Body));
comp_rule({fact, {atom, _, RuleName}, Head}) 
  when is_list(Head) ->
    pls:rule(RuleName, comp_expr_list(Head), []).


comp_expr_list(EL) when is_list(EL) ->
    [comp_expr(E) || E <- EL].


comp_expr({atom, _, A}) ->
    A;
comp_expr({integer, _, I}) ->
    I;
comp_expr({float, _, F}) ->
    F;
comp_expr({call, {atom, _, RuleName}, ArgSpec}) when is_list(ArgSpec) ->
    pls:call(RuleName, comp_expr_list(ArgSpec));
comp_expr({var, _, V}) ->
    pls:v(V).


file(TargetFile, OutputDir) when is_list(TargetFile), is_list(OutputDir) ->
    case pl_scan:file(TargetFile) of
        {ok, Tokens, _Cnt} when is_list(Tokens) ->
            case pl_parser:parse(Tokens) of
                {ok, ParseTree} ->
                    ModName = module_name(TargetFile),
                    OutFile = outfile_name(ModName),
                    OutPath = filename:join(OutputDir, OutFile),
                    case module(ModName, kb(ParseTree)) of
                        {ok, ModName, ModBin} ->
                            case file:write_file(OutPath, ModBin) of
                                ok ->
                                    ok;
                                {error, Reason} ->
                                    {error, {write, OutPath, Reason}}
                            end;
                        CompileError ->
                            {error, {compile, CompileError}}
                    end;
                ParseError ->
                    {error, {parse, ParseError}}
            end;
        ScanError ->
            {error, {scan, ScanError}}
    end.


module_name(TargetFile) when is_list(TargetFile) ->
    BaseName = filename:basename(TargetFile),
    case string:rstr(BaseName, ".pl") of
        0 ->
            throw({unknown_filename, TargetFile});
        N ->
            list_to_atom(string:substr(BaseName, 1, (N - 1)) ++ "_pl")
    end.


outfile_name(ModName) when is_atom(ModName) ->
    atom_to_list(ModName) ++ ".beam".


%% Compilation to Erlang bytecode:
module(Name, KB) when is_atom(Name), is_list(KB) ->
    ModuleAst = module_attr(Name),
    ExportAst = export_attr(),
    KBAst = kb_to_bytecode(KB),
    Forms = [erl_syntax:revert(Ast) || Ast <- [ModuleAst, ExportAst, KBAst]],
    compile:forms(Forms, [debug_info]).


module_attr(ModName) when is_atom(ModName) ->
    erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(ModName)]).


export_attr() ->
    %%?TODO: Export callable versions of each predicate defined in
    %% this module.
    erl_syntax:attribute(
      erl_syntax:atom(export),
      [erl_syntax:list([
                        erl_syntax:arity_qualifier(
                          erl_syntax:atom(kb),
                          erl_syntax:integer(0)
                         )
                       ])
      ]
     ).


%% rule_accessor(foo, 3) :
%%
%% foo(X, Y, Z) ->
%%     (paralog:assume_interpreter()):call(foo, [X, Y, Z]).
rule_accessor(RuleName, Arity) when is_atom(RuleName), is_integer(Arity) ->
    {Args, AssembledArgs} = notimplemented:build_args(Arity),
    Clause = erl_syntax:clause(
               Args,
               none,
               [erl_syntax:application(
                  erl_syntax:application(
                    erl_syntax:atom(paralog),
                    erl_syntax:atom(assume_interpreter),
                    []
                   ),
                  erl_syntax:atom(call),
                  [erl_syntax:atom(RuleName),
                   AssembledArgs
                  ]
                 )]),
    erl_syntax:function(erl_syntax:atom(RuleName), [Clause]).
      

%% %% Make this implementation not crap!!!
%% build_args(0) ->
%%     {[], []};
%% build_args(N) when is_integer(N) ->
%%     {AMinus, CMinus} = build_args(N - 1),
%%     NewArg = list_to_atom(erlang:ref_to_list(make_ref())),
%%     {[


kb_to_bytecode(KB) ->
    Clause = erl_syntax:clause(
               [],
               none,
               [erl_syntax:abstract(KB)]
              ),
    erl_syntax:function(erl_syntax:atom(kb), [Clause]).

